#include "csapp.h"
#include "sbuf.h"
#pragma GCC diagnostic ignored "-Wformat-overflow="
#define NTHREADS 4
#define SBUFSIZE 16
#define MAX_CACHE 10
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
typedef struct Lock Lock;
typedef struct Cache Cache;
static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n";
struct Lock{
	sem_t rlock;
	sem_t wlock;
	int readers;
};
struct Lock *lock;
void Lock_init(){
	lock->readers=0;
	sem_init(&lock->rlock,0,1);
	sem_init(&lock->wlock,0,1);
	return;
}
struct Cache{
	int times;
	char url[MAXLINE];
	char buf[MAX_OBJECT_SIZE];
};
struct Cache cache[MAX_CACHE];
int Max_lrutimes(){
	int i=0; int maxn=0;
	while(i<MAX_CACHE){
		if(cache[i].times>maxn) maxn=cache[i].times;
		++i;
	}
	return maxn;
}
char* Read_cache(char *url){
	sem_wait(&lock->rlock);
	if(lock->readers==1) sem_wait(&lock->wlock);
	lock->readers++;
	sem_post(&lock->rlock);
	char *buf=NULL;
	for(int i=0;i<MAX_CACHE;++i){
		if(strcmp(url,cache[i].url)==0){//HIT
			 buf = (char *)Malloc( sizeof( cache[i].buf ) );
			 memcpy(buf,cache[i].buf,sizeof(cache[i].buf));
			int maxlru=Max_lrutimes();
			cache[i].times=maxlru+1;
			break;
		}
	}
	sem_wait(&lock->rlock);
	--lock->readers;
	if(lock->readers==0){//Nobody are reading
		sem_post(&lock->wlock);
	}
	sem_post(&lock->rlock);
	return buf;
}
void Write_cache(char *buf,char *url){
	sem_wait(&lock->wlock);
	int index=0;
	while( index < MAX_CACHE ){
		//Find the inserted index
		if(cache[index].times == 0) break;
		++index;
	}
	//If all is full.Running the LRU
	if(index==MAX_CACHE){
		int minn=cache[0].times;
		for(int i=1;i<MAX_CACHE;++i){
			if(cache[i].times < minn){ minn=cache[i].times; index=i; }
		}
	}
	cache[index].times=Max_lrutimes()+1;
	//Why use the cache[index].xxx or strcpy is right?
	//But I use the sizeof(url) is wrong ?
	memcpy(cache[index].url,url,sizeof(cache[index].url));
	memcpy(cache[index].buf,buf,sizeof(cache[index].buf));
	sem_post(&lock->wlock);
	return;
}
sbuf_t sbuf;
void read_requesthdrs(rio_t *rio,int serverfd){
	char buf[MAXLINE];
	/* Prepare for static return */
	sprintf(buf,"%s",user_agent_hdr);
	sprintf(buf,"%sConnection: close\r\n",buf);
	sprintf(buf,"%sProxy-Connection: close\r\n",buf);
	Rio_writen(serverfd,buf,strlen(buf));
	/* Filter the extra request */
	while(1){
		Rio_readlineb(rio,buf,MAXLINE);
		if(!strcmp(buf,"\r\n")) break;
		if(strncmp("Host",buf,4)==0) continue;
		if(strncmp("User-Agent",buf,10)==0) continue;
		if(strncmp("Connection",buf,10)==0) continue;
		if(strncmp("Proxy-Connection",buf,16)==0) continue;
		printf("%s",buf);
		Rio_writen(serverfd,buf,strlen(buf));
	}
	Rio_writen(serverfd,buf,strlen(buf));
	return;
}
/*       hostname   port    path
uri : www.baidu.com:8080/home.html
*/
int parse_uri(char *uri, char *hostname, char *path,char *port,char *request_head){
	char *top,*tail;
	tail=uri+strlen(uri);
	char *begin,*end;
	begin=strstr(uri,"//");
	begin= begin==NULL?uri:begin+2;
	end=begin;
	while( *end!=':'&& *end!='/' ) ++end;
	memcpy(hostname,begin,end-begin);//GET host name
	if(*end==':'){
		begin=end+1; //port
		end=strstr(begin,"/");
		memcpy(port,begin,end-begin);
	}
	begin=end;
	memcpy(path,begin,(int)(tail-begin)+1);
	sprintf(request_head,"GET %s HTTP/1.0\r\nHOST: %s\r\n",path,hostname);
	return 1;
}
void return_content(int serverfd,int clientfd,char* uri){
	size_t n,cnt=0; char buf[MAXLINE];
	char store[MAX_OBJECT_SIZE]; rio_t rio;
	Rio_readinitb(&rio,serverfd);
	while( (n=Rio_readlineb(&rio,buf,MAXLINE)) !=0 ){
		Rio_writen(clientfd,buf,n);
		cnt+=n;
		if(cnt<=MAX_OBJECT_SIZE){
			sprintf(store,"%s%s",store,buf);
		}
	}
	if(cnt<=MAX_OBJECT_SIZE) Write_cache(store,uri);
	return;
}
void doit(int clientfd){
	int serverfd,is_static;
	char buf[MAXLINE],hostname[MAXLINE],port[MAXLINE],path[MAXLINE],request_head[MAXLINE];
	char method[MAXLINE],uri[MAXLINE],version[MAXLINE];
	rio_t rio;
	Rio_readinitb(&rio,clientfd);
	Rio_readlineb(&rio,buf,MAXLINE);
	sscanf(buf,"%s %s %s",method,uri,version);
	if(strcasecmp(method,"GET")){
		printf("Proxy can't implemented this method");
		return;
	}
	parse_uri(uri,hostname,path,port,request_head);
	char *res=NULL;
	if((res=Read_cache(uri))!=NULL){
		Rio_writen(clientfd,res,strlen(res));
		Free(res);
		return;
	}
	serverfd = Open_clientfd(hostname, port);
	Rio_writen(serverfd,request_head,strlen(request_head));
	read_requesthdrs(&rio,serverfd);
	return_content(serverfd,clientfd,uri);
	return;
}
void *thread(void *vargp){
	Pthread_detach( pthread_self() );
	Free(vargp);
	while(1){
		int clientfd=sbuf_remove(&sbuf);
		doit(clientfd);
		Close(clientfd);
	}
	return NULL;
}
int main(int argc,char **argv){
	int i,listenfd,clientfd;
	socklen_t len;
	struct sockaddr_storage clientaddr;
	char name[MAXLINE],port[MAXLINE];
	pthread_t tid;
	if(argc!=2){//Wrong usage
		fprintf(stderr,"usage: %s <port>\n",argv[0]);
		exit(1);
	}
	listenfd=Open_listenfd( argv[1] );
	sbuf_init(&sbuf,SBUFSIZE);
	lock = Malloc( sizeof(struct Lock) );
	Lock_init();
	for(i=0;i<NTHREADS;++i) Pthread_create(&tid,NULL,thread,NULL);
	while(1){
		len = sizeof( struct sockaddr_storage );
		clientfd = Accept(listenfd,(SA *)&clientaddr,&len);
		Getnameinfo((SA *)&clientaddr,len,name,MAXLINE,port,MAXLINE,0);
		printf("Accepted connection from(%s,%s)\n",name,port);
		sbuf_insert(&sbuf,clientfd);
	}
	Free(lock);
	exit(0);
}
